﻿using System;
using System.Collections.Generic;
using JetBlack.AuthFeedBus.Messages;
using JetBlack.Collections.Specialized;
using JetBlack.Diagnostics;

namespace JetBlack.AuthFeedBus.Distributor
{
    internal class Market : IDisposable
    {
        private readonly List<Interactor> interactors = new List<Interactor>();
        private readonly AsynchQueue<KeyValuePair<Interactor, Message>> messageQueue = new AsynchQueue<KeyValuePair<Interactor, Message>>(false);

        private readonly SubscriptionManager subscriptionManager = new SubscriptionManager();
        private readonly NotificationManager notificationManager = new NotificationManager();

        internal Market()
        {
            messageQueue.OnItemDequeued += OnMessageDequeued;

            subscriptionManager = new SubscriptionManager();
            subscriptionManager.OnSubscription += HandleForwardedSubscription;
            subscriptionManager.OnSubscriberMessage += HandleSubscriberMessage;
            subscriptionManager.OnClientMessage += HandleClientMessage;
            subscriptionManager.OnStaleTopic += HandleStaleTopic;

            notificationManager = new NotificationManager();
            notificationManager.OnNotificationResponse += HandleNotificationResponse;
        }

        internal void Start()
        {
            messageQueue.StartProcessing();
        }

        internal void HandleClientMessage(Interactor sender, Message message)
        {
            messageQueue.Enqueue(new KeyValuePair<Interactor, Message>(sender, message));
        }

        private void OnMessageDequeued(KeyValuePair<Interactor, Message> item)
        {
            OnMessageDequeued(item.Key, item.Value);
        }

        private void OnMessageDequeued(Interactor sender, Message message)
        {
            switch (message.MessageType)
            {
                case MessageType.SubscriberMessage:
                    HandlePublishSubscriberMessage(message as SubscriberMessage, sender);
                    break;
                case MessageType.ClientMessage:
                    HandleSendClientMessage(message as ClientMessage, sender);
                    break;
                case MessageType.NotificationRequest:
                    HandleNotificationRequest(message as NotificationRequest, sender);
                    break;
                case MessageType.SubscriptionRequest:
                    HandleSubscription(message as SubscriptionRequest, sender);
                    break;
                case MessageType.Entitlements:
                    HandleEntitlements(message as Entitlements, sender);
                    break;
                default:
                    throw new ArgumentException("invalid message type");
            }
        }

        internal void AddClient(Interactor client)
        {
            lock (interactors)
                interactors.Add(client);
        }

        internal void RemoveClient(Interactor client)
        {
            lock (subscriptionManager)
                subscriptionManager.Remove(client);

            lock (notificationManager)
                notificationManager.RemoveInteractor(client);

            lock (interactors)
                interactors.Remove(client);
        }

        private void HandleSubscription(SubscriptionRequest message, Interactor subscriber)
        {
            if (!subscriber.HasRole(message.Feed, ClientRole.Subscribe))
            {
                AppEnvironment.TraceSource.Error("Subscription denied for client \"{0}\" on feed \"{1}\"", subscriber, message.Feed);
                return;
            }

            lock (subscriptionManager)
                subscriptionManager.HandleSubscription(message, subscriber);
        }

        private void HandleNotificationRequest(NotificationRequest message, Interactor notifiable)
        {
            if (!notifiable.HasRole(message.Feed, ClientRole.Notify))
            {
                AppEnvironment.TraceSource.Error("Notification request denied for client \"{0}\" on feed \"{1}\"", notifiable, message.Feed);
                return;
            }

            lock (notificationManager)
                notificationManager.HandleRequest(message, notifiable);
        }

        private void HandlePublishSubscriberMessage(SubscriberMessage message, Interactor publisher)
        {
            if (!publisher.HasRole(message.Feed, ClientRole.Publish))
            {
                AppEnvironment.TraceSource.Error("Publish change request denied for client \"{0}\" on feed \"{1}\" with topic \"{2}\"", publisher, message.Feed, message.Topic);
                return;
            }

            lock (subscriptionManager)
                subscriptionManager.HandleSubscriberMessage(message, publisher);
        }

        private void HandleSendClientMessage(ClientMessage message, Interactor publisher)
        {
            if (!publisher.HasRole(message.Feed, ClientRole.Publish))
            {
                AppEnvironment.TraceSource.Error("Publish image request denied for client {0} on feed \"{1}\" with topic \"{2}\"", publisher, message.Feed, message.Topic);
                return;
            }

            lock (subscriptionManager)
                subscriptionManager.HandleClientMessage(message, publisher);
        }

        private void HandleEntitlements(Entitlements message, Interactor entitler)
        {
            if (!entitler.HasRole(message.Feed, ClientRole.Entitle))
            {
                AppEnvironment.TraceSource.Error("Entitlement request denied for client {0} on feed \"{1}\" with topic \"{2}\"", entitler, message.Feed, message.Topic);
                return;
            }

            lock (subscriptionManager)
                subscriptionManager.HandleEntitlement(message, entitler);
        }

        /// <summary>
        /// This forwards a subscription request from the subscription manager
        /// to the notification manager.
        /// 
        /// Any clients that have asked to be informed on a subscription which
        /// matches their pattern will be informed.
        /// will be
        /// </summary>
        /// <param name="message"></param>
        private void HandleForwardedSubscription(ForwardedSubscriptionRequest message)
        {
            lock (notificationManager)
                notificationManager.NotifyListeners(message);
        }

        private void HandleSubscriberMessage(Interactor publisher, Interactor subscriber, SubscriberMessage message)
        {
            subscriber.SendMessage(message);
        }

        private void HandleClientMessage(Interactor publisher, Interactor subscriber, ClientMessage message)
        {
            subscriber.SendMessage(message);
        }

        private void HandleNotificationResponse(Interactor notifiable, IEnumerable<string> feeds, bool isPublisher, bool isAdd)
        {
            lock (subscriptionManager)
                subscriptionManager.HandleNotificationResponse(notifiable, feeds, isPublisher, isAdd);
        }

        private void HandleStaleTopic(Interactor publisher, Interactor subscriber, string feed, string topic)
        {
            subscriber.SendMessage(new SubscriberMessage(feed, topic, true, null));
        }

        public void Dispose()
        {
            messageQueue.Dispose();
        }
    }
}
